Repository navigation
[http-client-csharp] Preserve shipped date-time property names - #11773
JoshLove-msft merged 24 commits into
Conversation
commit: |
|
No changes needing a change description found. |
There was a problem hiding this comment.
Pull request overview
This PR updates the C# HTTP client generator’s backward-compatibility naming logic so that date-time property names previously shipped in a GA baseline are preserved (e.g., StartOn, EndOn, ExpireOn), while new properties still follow the canonical date-time normalization rules.
Changes:
- Extend date-time suffix normalization to treat
Expire*consistently (e.g.,ExpireOn→ExpiresOn) inCSharpNameExtensions. - Update
PropertyProviderto preserve the exact property name from the last contract when it normalizes to the current canonical name. - Add unit tests + last-contract test data covering previously shipped historical names and already-canonical baselines.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs | Preserve historical GA date-time property names by matching last-contract members against canonical normalized names. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs | Add Expire -> Expires mapping for date-time verb normalization. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs | Add regression tests for preserving historical GA property names and for already-canonical contracts. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/PropertyProviderTests/TestPropertyNamePreservesPreviouslyGeneratedDateTimeSuffixFromLastContract/TestModel.cs | Last-contract baseline containing StartOn for preservation testing. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/PropertyProviderTests/TestPropertyNamePreservesHistoricalDateTimeNamesFromLastContract/TestModels.cs | Last-contract baseline containing historical names (EndOn, ExpireOn, etc.) and canonical StartsOn. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs | Add normalization tests for ExpireOn/expireOn to produce ExpiresOn/expiresOn. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Full Azure-branded DPG regeneration completed successfully with the latest changes: all 47 libraries regenerated and there are no resulting SDK file diffs. Draft regeneration PR: Azure/azure-sdk-for-net#62499 - by copilot |
Address review feedback on preserving shipped date-time property names. Customization matching recomputed the canonical name (StartsOn) to decide which generated property a customization replaces. Now that a property can be emitted under a name preserved from the last contract (StartOn), that recomputation no longer matched, so: - A raw-name customization failed to suppress the generated property and the property was emitted twice. Suppression now derives the name from the constructed PropertyProvider instead of recomputing it. - A customization written against the preserved name failed to resolve back to its spec property in the canonical view, silently losing WireInfo and the serialized name. The canonical view now also indexes spec properties under the names the generator actually produced. Add the projected-provider regression case called for by the linked issue, where the public GA member lives on a different generated provider than the wire-model property, and validate all new tests against TestData so the preserved name is checked through constructors and not just property names. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a5e8332-8ebe-44b1-97c5-44cc31caf25e
|
Thanks for the re-review and mutation testing. Addressed the two suggested follow-ups in
I also renamed For the declaration-order report from Copilot, I reversed the GA fixture to declare The focused property/customization/name suite passes (254 tests). - by copilot |
|
Addendum — my re-review above landed ten seconds after
|
| Guard removed | Suite result |
|---|---|
the else (rebuild the map even when knownSpecProperties was supplied) |
fails DuplicateExactIdentifierCustomizationSuppressesOnlyFirstSpecMatch |
exactNames.Add(name) in AddExactName |
fails DuplicateExactIdentifierCustomizationSuppressesOnlyFirstSpecMatch |
Both covered, and the test is a good one — asserting the surviving property and that both wire names remain on the canonical view catches the over-suppression directly rather than by proxy.
I traced the else specifically, since "stop populating this map on one path" is the kind of change that tends to lose something quietly. The only caller supplying knownSpecProperties is CanonicalTypeProvider.BuildProperties, which passes the generated provider's complete map; the entries the removed rebuild would have added come from generatedProperties, whose spec names are a subset of that map because it was built pre-filter. So nothing is dropped and the comment is accurate. It also composes correctly with the activeProperties guard from a3572f466: the map's values are the same PropertyProvider instances as in propertiesToFilter, so filtered-out properties remain in the map but fail the reference check, which is the intended outcome.
Making AddExactName's per-pass first-wins mirror _exactSpecPropertyNames in CanonicalTypeProvider is nice — the two places that resolve exact-vs-alias precedence now read the same way.
The revert
Reverting 3b5aaa62a was the right call, and #11783 is the right home for it. A change that reaches into property-build, constructor, serialization, and visitor lifecycle does not belong in a back-compat naming fix, and validating it against the real management-plane flattening path is exactly the prerequisite.
I verified the revert is clean rather than taking it on trust: PropertyProvider.cs and PropertyProviderTests.cs are byte-identical between a3572f466 and 83d94f97f, and neither prototype test-data directory survives in the tree. Since b7b952eb8 landed between the prototype and its revert and touched TypeProvider.cs, that was worth confirming.
Still open from the re-review above
Neither is a blocker; restating so they don't get lost to the comment-ordering collision:
new HashSet<PropertyProvider>(propertiesToFilter)depends on reference equality, which holds today but is not enforced —PropertyProvideris notsealed, so a derived provider overridingEquals/GetHashCodewould silently turn the stale-alias filter into a value match with no test failing.ReferenceEqualityComparer.Instancewould pin it.- The spec-side
!identifierName.EndsWith("On", StringComparison.Ordinal)guard is covered only byExactSpecNameTakesPrecedenceOverCanonicalAliasForCustomization, a test named for a different concern, and the inline comment explains only the contract-sideOnrequirement.
Plus the two cosmetic notes (_nounToVerbMap is now an array, and _ = Properties; re-entrancy).
State of the PR from my side: the core change is sound, every behavioral guard I could identify is individually covered by a test that fails without it, and the full 2105-test suite is green at 83d94f97f.
--generated by Copilot
|
Addressed the remaining cosmetic notes in
The earlier The focused suite remains green at 254 tests. - by copilot |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 46 out of 46 changed files in this pull request and generated no new comments.
Suppressed comments (1)
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs:280
- This ownership check only enumerates custom members declared on the current type, while
FilterCustomizedPropertiesalso treats custom members inherited fromBaseTypeProvideras claims (TypeProvider.cs:98-149). Consequently, if a base customization explicitly renames an unrelated member toStartOn, a derivedstartTimeproperty can preserve the GAStartOnhere and then be removed by filtering instead of falling back toStartsOn, losing the derived spec property. Apply the same base-type traversal used by customization filtering when collecting explicitly renamed claims.
private static IEnumerable<(string Name, string OriginalName)> GetExplicitlyRenamedCustomNames(TypeProvider enclosingType)
{
foreach (var customProperty in enclosingType.CustomCodeView?.Properties ?? [])
{
if (customProperty.OriginalName is { } propertyOriginalName)
This reverts commit 001c070.
|
Re-reviewed at Verification
Both prior follow-ups addressed
I also noticed Mutation results
The fixture reversal is a genuine strengtheningReversing
Because the lookup uses
|
Summary
ExpireTimeandExpireOntoExpiresOnfor new APIs, makingExpireOnandExpirationTimeshare the semanticExpiresstemFixes #11772
Validation
dotnet test packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Microsoft.TypeSpec.Generator.Tests.csproj --no-restore --filter 'FullyQualifiedName~PropertyProviderTests|FullyQualifiedName~ModelCustomizationTests|FullyQualifiedName~CSharpNameExtensionsTests' -- NUnit.NumberOfTestWorkers=1